Admission Control

개요

admission 번역

admission을 번역하기 쉽지는 않은데, 나는 일단 "승인" 이라고 하려고 한다.
인증은 누구인지 판별하는 단계, 인가는 어떤 권한을 허락하는 단계, 승인은 최종 관문으로서 세부 스펙을 검사하고, 가능한 방식으로 조정하여 요청을 받아들이는 것이라고 이해하면 그럭저럭 말이 되는 것 같다고 생각했다.

API 접근 제어의 3,4번째 단계를 담당하는 admission control.
앞 단계, 쿠버네티스 인가, 쿠버네티스 인증을 전부 거치고 난 이후, 마지막으로 적용되는 제어 단계이다.
이 단계에서는 훨씬 더 상세한 제어가 가능하다.
가령 신뢰된 유저가 들어와서 파드에 대해 create 동작을 허가 받았다.
다만 이 유저가 파드를 만들 때 특정 볼륨을 마운팅하지 않길 바라거나, 제한된 이미지만 이용할 수 있게 만들고 싶다면 이 유저가 보내는 요청 내용을 뜯어서 살펴봐야만 한다.
이럴 때 사용할 수 있는 것이 바로 승인 제어다.

특징

승인 제어는 컨트롤러로서 구현된다.
즉, admission controller라는 놈이 kube-apiserver에 들어있어서 이 놈이 각종 승인 관련 제어를 진행한다.
(kube-controller-manager에 들어있지 않다.)
이 친구는 apiserver가 요청을 받아 수행하여 영구적으로 클러스터에 조작을 가하고 기록을 남기기 직전에 최종적인 단계로서 요청을 인터셉트하여 동작한다.
다양한 승인 제어 플러그인을 설정할 수 있는데, 이들은 전부 순서대로 적용된다.

인증 인가 모듈과 다르게, 승인 제어 모듈에서 거부가 한번이라도 일어나면 요청은 바로 거절된다.
거절되는 오브젝트에 대해 승인 제어는 복잡한 필드 기본값을 둘 수 있다.

쿠버네티스의 중요한 기능들은 사실 이 승인 제어를 통해서 이뤄지는 것이 조금 있다.
그래서 어드미션 컨트롤을 끄게 되면 생각했던 대로 쿠버네티스가 동작하지 않게 될 가능성이 높다.

또 한 가지, 알아야 할 것은 제어가 생성, 변경, 삭제, 혹은 오브젝트 연결 등의 동작을 수행하는 요청에만 동작한다는 것이다.
아무래도 이건 조작을 가하는 요청에 대해 동작하기 위해 존재하다보니, 읽기 관련 요청은 그대로 우회된다.

구조

|800
구체적으로 들어가면 승인 제어는 두 가지 단계로 나뉜다.
그림에서는 동적 승인 제어 설정을 위한 웹훅에 대한 내용도 담겨 있어서 추가적인 플로우가 들어가 있으나, 기본은 어디까지나 Mutating, Validating 이라는 두 단계 뿐이다.

Mutating 단계

들어온 요청을 조작한다.
가령 기본 네임스페이스에서 파드를 만드는 모든 요청에 대해 초기화 컨테이너를 적용시키고 싶을 때, 이 단계에 적절한 플러그인을 적용해주면 된다.
조건에 걸리는 모든 플러그인들이 해당 요청에 대해 조작을 수행할 것이다.
조작을 가하는 만큼, 각 플러그인이 충돌이 일어나지 않게 하거나 순서를 제대로 지정해주는 것이 관리 포인트 중 하나라 할 수 있다.

Validating 단계

변형이 끝나고 나면 마지막으로 검증을 수행한다.
이 단계에서 요청을 거부할지 결정을 내리게 된다.
기본 네임스페이스에서는 디플로이먼트에 레플리카를 5개 이상 만들지 못하도록 하고 싶고, 이를 벗어난 요청이 일어났을 때는 거부하고 싶다면 이 단계에 플러그인을 적용해준다.

설정

그래서 이 플러그인을 어떻게 설정하는가?
기본적으로 쿠버네티스에서는 여러 내장 플러그인을 지원한다.
종류가 많기에 플러그인들을 전부 보지는 않겠다.
알아보고 싶다면 직접 문서 참조..
한번 보면 이 친구들이 정말 중요한 역할을 한다는 것을 알 수 있을 것이다.
가령 기본 스토리지 클래스를 넣어준다던가.. 하는 것들이 사실 여기에서 다 수행되는 것이다.

kube-apiserver -h | grep enable-admission-plugins

api 서버에 이렇게 명령을 내리면 현재 적용된 플러그인들을 확인할 수 있다.

kube-apiserver --enable-admission-plugins=NamespaceLifecycle,..
kube-apiserver --disable-admission-plugins=PodNodeSelector,..

적용하고 싶은 플러그인이 있다면 이렇게 인자로 넣어주면 된다.

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
  - name: ImagePolicyWebhook
    path: imagepolicyconfig.yaml

어드미션도 설정 파일을 이용해 세팅하는 것이 가능하다.
--admission-control-config-file에 경로를 명시하면 되는데, 위처럼 각 플러그인을 파일을 타고 들어가게 하는 게 가능하다.
이렇게 세팅할 경우 각 플러그인에 대한 세부적인 세팅을 하는 것이 가능하기 때문에 조금 더 추천된다.
무엇보다, 아래에서 볼 동적 승인 제어 확장을 이용하기 위해서는 파일을 이용하는 것이 필수적이다.

동적 확장

플러그인 중에서는 현재 기준 3가지 특별한 플러그인이 있다.
이들은 기본 세팅된 플러그인 이상으로 관리자가 각종 정책을 커스텀할 수 있도록 하는 기능을 제공한다.

이건 Kyverno에서 제공하는 간단한 그림인데, 요지는 각 단계에서 웹훅과 같은 방법으로 동적으로 추가 승인 제어 정책을 넣어줄 수 있다는 것으로 보면 되겠다.

Mutating & Validating Admission Webhook

말 그대로 승인 제어를 웹훅을 통해 조작하는 방식이다.

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ValidatingAdmissionWebhook
  configuration:
    apiVersion: apiserver.config.k8s.io/v1
    kind: WebhookAdmissionConfiguration
    kubeConfigFile: "<path-to-kubeconfig-file>"
- name: MutatingAdmissionWebhook
  configuration:
    apiVersion: apiserver.config.k8s.io/v1
    kind: WebhookAdmissionConfiguration
    kubeConfigFile: "<path-to-kubeconfig-file>"

api 서버에 이렇게 인자를 넣어주면, 승인 제어를 바라는 요청을 웹훅으로 날릴 수 있게 된다.
각 필드에 하위로 configuration이 들어가는 것이 보인다.
이것은 어떤 오브젝트를 관련 웹훅 설정 내용으로 삼겠냐는 지정을 하는 필드이다.
이 다음부터는 클러스터에 관련 오브젝트를 넣어주면 된다!

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: "pod-policy.example.com"
webhooks:
- name: "pod-policy.example.com"
  objectSelector:
    matchLabels:
      foo: bar
  rules:
  - apiGroups:   [""]
    apiVersions: ["v1"]
    operations:  ["CREATE"]
    resources:   ["pods"]
    scope:       "Namespaced"
  clientConfig:
    service:
      namespace: "example-namespace"
      name: "example-service"
    caBundle: <CA_BUNDLE>
  admissionReviewVersions: ["v1"]
  sideEffects: None
  timeoutSeconds: 5

이런 식으로 어떤 트래픽들에 대해서 웹훅을 쏘고, 어떻게 통신할 것인지를 담는 설정을 넣으면 된다.
objectSelector, rules 필드를 이용해 어떤 요청들에 대해 웹훅을 적용할지 세팅한다.
여기에 clientConfig에서 어디로 트래픽을 날릴지 경로를 명시하면 된다.
보통 흔히 사용하는 방식은 클러스터 내부에 웹훅을 처리하는 파드를 띄워 클러스터 내부에서 승인 제어를 처리하는 것이다.
대표적으로 KEDA가 이러한 방식을 사용한다.

  clientConfig:
    url: "https://my-webhook.example.com:9443/my-webhook-path"

만약 외부 서버를 쓰고 싶다면 이런 식으로 하는 것도 가능하다.

scope는 Cluster, Namespaced가 가능하다.
기본 웹훅 타임아웃은 10초이다.
관련 필드 내용은 어드미션 웹훅에 대충 정리했는데, 나중에 정리해서 이 문서로 옮기고자 한다.

굉장히 중요한 포인트가 하나 있다.
변형 승인 제어의 경우 같은 대상에 대해 동작하는 복수의 웹훅이 있을 수 있다.
이때 어떤 웹훅이 먼저 적용될지에 대한 순서는 보장되지 않는다.
그래서 다른 웹훅이 변형을 가했을 때 이것을 이전 웹훅이 다시 변형을 할지에 대해 엄밀하게 조정을 할 필요가 있다.
reinvoctationPolicy 참고.

요청 내용

웹훅은 Content-Type: application/json인 POST 요청으로 날아간다.
바디에 들어가는 것은 admission.k8s.io api의 AdmissionReview 오브젝트이다.

apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
  # 어드미션 호출을 식별하는 랜덤 UID
  uid: 705ab4f5-6393-11e8-b7cc-42010a800002
  # 정확하게 어떤 오브젝트를 보낼 것인지 식별할 수 있게 group, version, kind를 명시해야 한다.
  kind:
    group: autoscaling
    version: v1
    kind: Scale
  # 변형하고자 하는 리소스를 명시한다.
  resource:
    group: apps
    version: v1
    resource: deployments
  # 서브 리소스에 대해서 적용하는 거라면 적는다.
  subResource: scale
  
  # api 서버가 받은 원본 오브젝트,리소스 등을 정확하게 적어준다.
  # matchPolicy: Equivalent이고, 원본 요청이 웹훅에 등록되기 위해 버전이 변경됐을 때만 위의 kind와 다를 것이다.
  requestKind:
    group: autoscaling
    version: v1
    kind: Scale
  requestResource:
    group: apps
    version: v1
    resource: deployments
  requestSubResource: scale

  # 변경돼야 할 리소스 이름
  name: my-deployment
  namespace: my-namespace
  # CREATE, UPDATE, DELETE 혹은 CONNECT가 가능하다.
  operation: UPDATE

  # 원본 요청을 날렸던 유저 인증 정보
  userInfo:
    username: admin
    uid: 014fbff9a07c
    groups:
      - system:authenticated
      - my-admin-group
    # 임의의 필드이고, 인증 단계에서 필요한 값을 채워서 보내주면 된다.
    # SubjectAccessReview 체크가 이뤄질 때는 반드시 세팅돼야 한다.
    extra:
      some-key:
        - some-value1
        - some-value2

  # 변형되어 만들어질 오브젝트로, DELETE 동작이라면 null일 것이다.
  object:
    apiVersion: autoscaling/v1
    kind: Scale
  # 웹훅이 적용되는, 원래 클러스터에 있던 오브젝트 정보로, CREATE, CONNECT 동작이라면 null일 것이다.
  oldObject:
    apiVersion: autoscaling/v1
    kind: Scale
  # 수행될 동작에 대한 옵션으로, CONNECT 동작에 대해 null일 것이다.
  options:
    apiVersion: meta.k8s.io/v1
    kind: UpdateOptions
  # 말 그대로 드라이런을 할지를 나타내는 필드.
  dryRun: False

(이거 내가 yaml 형식으로 바꿨나..?)
yaml 형식으로 작성됐는데, 실제로는 당연히 json 형식으로 요청이 날아간다.
이것은 디플로이먼트에 대한 scale 동작에 대한 어드미션을 리뷰하는 오브젝트이다.
반드시 버전이 잘 일치해야 한다는 것에 유의하자.

응답 내용

응답 시에도 마찬가지로 같은 버전으로, 같은 방식으로 돌아오면 된다.

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": false,
    "status": {
      "code": 403,
      "message": "You cannot do this because it is Tuesday and your name starts with A"
    }
  }
}

응답은 이렇게, 버전과 kind가 일치해야 한다.
또한 uid와 allowed를 반드시 담아서 와야 한다.

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true,
    "patchType": "JSONPatch",
    "patch": "W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0="
  }
}

mutating webhook이라면 여기에 변형을 가할 수도 있어야할 것이다.
그래서 이렇게 보낸다.
json Patch 타입으로 보내면 되고, [{"op": "add", "path": "/spec/replicas", "value": 3}]를 base64로 인코딩한 방식이다.

{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "response": {
    "uid": "<value from request.uid>",
    "allowed": true,
    "warnings": [
      "duplicate envvar entries specified with name MY_ENV",
      "memory request less than 4MB specified for container mycontainer, which will not start successfully"
    ]
  }
}

이렇게 HTTP warning을 간편하게 보낼 수도 있다.
어차피 앞에 알아서 "Warning: "이 붙을 것이니 굳이 붙이지 말고, 120자를 넘어가면 잘릴 수도 있다.

Validating Admission Policy

웹훅 방식은 웹훅 서버를 따로 만들어야 한다는, 주체에 따라서는 굉장히 운영 비용이 발생하는 일이 될 수 있다.
이를 위해 쿠버네티스에서는 클러스터 자체적으로 동적으로 승인 제어를 지정할 수 있도록 하는 시도를 하고 있는데, 그 일환 중 하나가 바로 검증 승인 정책이다.[1]
이 방식은 CEL 표현식을 통해 검증을 수행할 수 있도록 한다.
아직 알파 단계이지만 Mutating Admission Policy도 개발되는 중이다.[2]

이런 내장 오브젝트가 생겨남에 따라, Kyverno와 같은 확장 승인 애드온들의 사용처가 점차 없어질 것이라는 이야기가 있다.
내 생각에도 그럴 것 같기는 한다.
다만 CEL 이란 방식으로 정책을 한정하고 있기에 이미 다른 솔루션을 사용하는 운영 조직에서는 굳이 이를 마이그레이션할 필요가 없을 것 같기도 하다.

관련 문서

이름 noteType created
Admission Control knowledge 2025-01-20
Admission Webhook knowledge 2025-01-20
Validation Admission Policy knowledge 2025-03-17
Kyverno knowledge 2025-03-17
6W - api 구조와 보안 1 - 인증 published 2025-03-15
6W - api 보안 2 - 인가, 어드미션 제어 published 2025-03-16
E-Kyverno 기본 실습 topic/explain 2025-03-17
E-검증 승인 정책 실습 topic/explain 2025-03-17
S-exec 명령어가 승인 제어에 걸리는 이유 topic/shooting 2025-03-17

참고


  1. https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/ ↩︎

  2. https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/ ↩︎